package com.mi.system.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mi.common.core.constant.enums.RoleEnum;
import com.mi.common.core.exception.ServiceException;
import com.mi.common.security.config.JwtProperties;
import com.mi.common.security.util.JwtPayload;
import com.mi.common.security.util.JwtUtil;
import com.mi.common.security.util.SecurityUtils;
import com.mi.system.dto.query.UserQueryDTO;
import com.mi.system.dto.request.UserEnableRequestDTO;
import com.mi.system.dto.request.UserLoginRequestDTO;
import com.mi.system.dto.request.UserModifyPasswordRequestDTO;
import com.mi.system.dto.request.UserRequestDTO;
import com.mi.system.entity.Role;
import com.mi.system.entity.User;
import com.mi.system.entity.UserRole;
import com.mi.system.mapper.UserMapper;
import com.mi.system.vo.UserAuthVO;
import com.mi.system.vo.UserVO;
import com.nimbusds.jose.JOSEException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.time.Instant;
import java.util.List;

import static com.mi.common.core.constant.CommonConstants.ENABLED;

/**
 * User service
 *
 * @author jin_huaquan
 * @version 1.0
 * @date 2021/11/1 14:05
 */
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private static final String USERNAME_ADMIN = "admin";
    private static final String PASSWORD_ADMIN = "admin";
    private final ObjectMapper objectMapper;
    private final JwtProperties jwtProperties;
    private final PasswordEncoder passwordEncoder;
    private final UserRoleService userRoleService;
    private final AuthenticationManager authenticationManager;
    private final RoleService roleService;

    @PostConstruct
    public void initAdmin() {
        boolean exists = existsByUsername(USERNAME_ADMIN);
        if (exists) {
            return;
        }
        Role adminRole = roleService.getOne(Wrappers.<Role>lambdaQuery().eq(Role::getRoleCode, RoleEnum.ADMIN.getCode()));

        User user = new User();

        user.setUsername(USERNAME_ADMIN);
        user.setMobile(USERNAME_ADMIN);
        user.setEnabled(ENABLED);
        user.setDeptId(0L);
        user.setPassword(passwordEncoder.encode(PASSWORD_ADMIN));
        user.setRemarks("默认管理员用户");

        save(user);

        UserRole userRole = new UserRole();
        userRole.setRoleId(adminRole.getId());
        userRole.setUserId(user.getId());

        userRoleService.save(userRole);
    }

    @Override
    public Page<UserVO> listUser(Page page, UserQueryDTO query) {
        return baseMapper.selectByPage(page, query);
    }

    @Override
    public List<UserVO> listUser(UserQueryDTO query) {
        return baseMapper.selectByQuery(query);
    }

    @Override
    public UserVO getCurrentUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Assert.notNull(authentication, "登录已失效，请重新登录");

        String username = authentication.getName();

        return getUser(username);
    }

    @Override
    public UserVO getUser(String username) {
        return baseMapper.selectByUsername(username);
    }

    @Override
    @SneakyThrows(JsonProcessingException.class)
    public UserAuthVO login(UserLoginRequestDTO request) {
        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(
                request.getUsername(), request.getPassword());
        final Authentication authentication = authenticationManager.authenticate(upToken);
        SecurityContext ctx = SecurityContextHolder.createEmptyContext();
        SecurityContextHolder.setContext(ctx);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        JwtPayload jwtPayload = JwtPayload.builder()
                .exp(jwtProperties.getExpiresIn())
                .nbf(Instant.now().toEpochMilli())
                .username(request.getUsername())
                .build();

        String accessToken;
        try {
            accessToken = JwtUtil.generateToken(objectMapper.writeValueAsString(jwtPayload), jwtProperties.getSecret());
        } catch (JOSEException e) {
            throw new ServiceException("JWT token generates failed.", e);
        }

        UserVO user = getUser(request.getUsername());

        UserAuthVO userAuth = new UserAuthVO();

        BeanUtil.copyProperties(user, userAuth);
        userAuth.setAccessToken(accessToken);
        userAuth.setTokenType(jwtProperties.getTokenType());

        return userAuth;
    }

    @Override
    @Transactional(rollbackFor = TransactionException.class)
    public boolean saveUser(UserRequestDTO request) {
        Assert.isFalse(existsByUsername(request.getUsername()), "已存在相同的账户名");
        Assert.isFalse(existsByMobile(request.getMobile()), "手机号已被占用");

        User user = new User();

        BeanUtil.copyProperties(request, user);
        user.setPassword(passwordEncoder.encode(request.getPassword()));

        this.save(user);

        return userRoleService.saveUserRole(request.getRoleIds(), user.getId());
    }

    @Override
    @Transactional(rollbackFor = TransactionException.class)
    public boolean updateUser(Long id, UserRequestDTO request) {
        User user = baseMapper.selectById(id);
        Assert.notNull(user, "未找到对应的用户");

        user.setDeptId(request.getDeptId());
        user.setEnabled(request.getEnabled());
        user.setRemarks(request.getRemarks());

        this.updateById(user);

        return userRoleService.updateUserRole(request.getRoleIds(), id);
    }

    @Override
    @Transactional(rollbackFor = TransactionException.class)
    public boolean enableUser(Long id, UserEnableRequestDTO request) {
        User user = baseMapper.selectById(id);
        Assert.notNull(user, "未找到对应的用户");

        user.setEnabled(request.getEnabled());
        return this.updateById(user);
    }

    @Override
    @Transactional(rollbackFor = TransactionException.class)
    public boolean modifyPassword(Long id, UserModifyPasswordRequestDTO request) {
        User user = baseMapper.selectById(id);
        Assert.notNull(user, "未找到对应的用户");

        user.setPassword(passwordEncoder.encode(request.getPassword()));

        return this.updateById(user);
    }

    @Override
    @Transactional(rollbackFor = TransactionException.class)
    public boolean modifyPassword(UserModifyPasswordRequestDTO request) {
        Long id = SecurityUtils.getUserId();
        User user = baseMapper.selectById(id);
        Assert.notNull(user, "未找到对应的用户");
        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(
                user.getUsername(), request.getOldPassword());
        try {
            authenticationManager.authenticate(upToken);
        } catch (AuthenticationException e) {
            throw new IllegalArgumentException("原密码错误");
        }

        user.setPassword(passwordEncoder.encode(request.getPassword()));

        return this.updateById(user);
    }

    @Override
    @Transactional(rollbackFor = TransactionException.class)
    public User removeUser(Long id) {
        User user = baseMapper.selectById(id);
        Assert.notNull(user, "未找到对应的用户");

        baseMapper.deleteById(id);
        userRoleService.removeUserRole(id);

        return user;
    }

    /**
     * whether exists with username
     *
     * @param username username
     * @return whether exists
     */
    private boolean existsByUsername(String username) {
        return baseMapper.selectCount(Wrappers.<User>lambdaQuery().eq(User::getUsername, username)) > 0;
    }

    /**
     * whether exists with mobile
     *
     * @param mobile mobile
     * @return whether exists
     */
    private boolean existsByMobile(String mobile) {
        return baseMapper.selectCount(Wrappers.<User>lambdaQuery().eq(User::getMobile, mobile)) > 0;
    }
}
